/*
 * MIT License
 * 
 * Copyright (c) 2017 wen.gu <454727014@qq.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*****************************************************************************
 * Name:simple_unit_test.h
 *
 * Purpose: simple unit test framework
 *
 * Developer:
       wen.gu,2015-12-29
 *
 *****************************************************************************/

#include "simple_unit_test.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
/***************************************************************************
 *
 * macro define
 *
 ***************************************************************************/
 
/***************************************************************************
 *
 * data struct define
 *
 ***************************************************************************/
/***************************************************************************
 *
 * inner define
 *
 ***************************************************************************/
enum test_msg_color {  
    COLOR_DEFAULT = 0,
    COLOR_RED,
    COLOR_GREEN,
    COLOR_YELLOW
};

#if defined(SU_WINDOWS)
#include <windows.h>
// Returns the character attribute for the given color.
static WORD GetColorAttribute(test_msg_color color) 
{  
    WORD colorAttr = 0;
    switch (color)
    {    
    case COLOR_RED:    colorAttr = FOREGROUND_RED; break;   
    case COLOR_GREEN:  colorAttr = FOREGROUND_GREEN; break;
    case COLOR_YELLOW: colorAttr = FOREGROUND_RED | FOREGROUND_GREEN; break;
    default:           colorAttr = 0; break;
    }

    return colorAttr;
}
#elif defined(SU_LINUX)
// Returns the ANSI color code for the given color.  COLOR_DEFAULT is
// an invalid input.
const char* GetAnsiColorCode(test_msg_color color) 
{  
    switch (color) 
    {    
    case COLOR_RED:     return "31";    
    case COLOR_GREEN:   return "32";    
    case COLOR_YELLOW:  return "33";    
    default:            return "0";  
    };
}
#endif

void ColoredPrintf(test_msg_color color, const char* fmt, ...) 
{  
    va_list args;  
    va_start(args, fmt);
#if defined(SU_WINDOWS)
    const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); 
    // Gets the current text color.  
    CONSOLE_SCREEN_BUFFER_INFO buffer_info;  
    GetConsoleScreenBufferInfo(stdout_handle, &buffer_info);  
    const WORD old_color_attrs = buffer_info.wAttributes;  
    // We need to flush the stream buffers into the console before each  
    // SetConsoleTextAttribute call lest it affect the text that is already  
    // printed but has not yet reached the console.  
    fflush(stdout);  
    SetConsoleTextAttribute(stdout_handle, GetColorAttribute(color) | FOREGROUND_INTENSITY); 
    vprintf(fmt, args);  
    fflush(stdout);  
    // Restores the text color.  
    SetConsoleTextAttribute(stdout_handle, old_color_attrs);

#elif defined(SU_LINUX)
      printf("\033[0;%sm", GetAnsiColorCode(color));  
      vprintf(fmt, args);  
      printf("\033[m");  // Resets the terminal to default.
#else//defualt havn't color
    vprintf(fmt, args);
#endif
    va_end(args);
}

static const char* formatTestCount(size_t testCount)
{
    return (testCount > 1) ? "tests" : "test";
}

static const char* formatTestCaseCount(size_t testCaseCount)
{
    return (testCaseCount > 1) ? "test cases" : "test case";
}

 /***************************************************************************
 *
 * API define
 *
 ***************************************************************************/




namespace simpleUnitTest{




/***************************************************************************
 * test class implement
 ***************************************************************************/
Test::Test(const char* testName)
    :mTestResult(UT_OK)
    //,mTestName(testName)//android ndk cann't support this constructor
{
    mTestName.append(testName);
}

Test::~Test()
{
    //todo
}

const char* Test::getTestName() const
{
    return mTestName.c_str();
}

int Test::getTestResult() const
{
    return mTestResult;
}

void Test::setTestResult(int result)
{
    mTestResult = result;
}


/***************************************************************************
 * test case class implement
 ***************************************************************************/
TestCase::TestCase(const char* caseName)
    :mPassedCnt(0)
    ,mFailedCnt(0)
    ,mCurrentTest(NULL)
    //,mCaseName(caseName) //android ndk cann't support this constructor
{
    //todo
    mTests.clear();
    mCaseName.append(caseName);
}

TestCase::~TestCase()
{
    //todo
    size_t testlen = mTests.size();
    for (size_t i = 0; i < testlen; i++)
    {
        Test* t = mTests[i];

        if (t)
        {
            delete t;
        }
    }

    mTests.clear();
}

const char* TestCase::getCaseName() const
{
    return mCaseName.c_str();
}


Test* TestCase::getCurrentTest() const
{
    return mCurrentTest;
}

Test* TestCase::addTest(Test* test)
{
    mTests.push_back(test);
    return test;
}

const vector<Test*>& TestCase::getTestList() const
{
    return mTests;
}

size_t TestCase::getTestCount()
{
    return mTests.size();
}

size_t TestCase::getPassedCount()
{
    return mPassedCnt;
}

size_t TestCase::getFailedCount()
{
    return mFailedCnt;
}

int TestCase::runCase()
{
    int testRet = UT_OK;
    size_t testsLen = mTests.size();
    const char* testInfo = (testsLen > 1) ? "tests" : "test";
    ColoredPrintf(COLOR_GREEN, "[----------]");
    ColoredPrintf(COLOR_DEFAULT,"%d %s from %s\n", testsLen, testInfo, mCaseName.c_str());

    for (size_t i = 0; i < testsLen; i++)
    {
        mCurrentTest = mTests[i];

        if (mCurrentTest)
        {
            ColoredPrintf(COLOR_GREEN, "[ RUN      ]");
            ColoredPrintf(COLOR_DEFAULT, "%s.%s\n", mCaseName.c_str(), mCurrentTest->getTestName());
            mCurrentTest->testBody();
            testRet = mCurrentTest->getTestResult();

            //todo add spend time
            if (testRet == UT_OK)
            {
                ColoredPrintf(COLOR_GREEN, "[       OK ]");
                ColoredPrintf(COLOR_DEFAULT, "%s.%s\n", mCaseName.c_str(), mCurrentTest->getTestName());
                mPassedCnt++;
            }
            else
            {
                ColoredPrintf(COLOR_RED, "[  FAILED  ]");
                ColoredPrintf(COLOR_DEFAULT, "%s.%s\n", mCaseName.c_str(), mCurrentTest->getTestName());
                mFailedCnt++;
            }
        }
    }

    //todo add spend time
    ColoredPrintf(COLOR_GREEN,"[----------]");
    ColoredPrintf(COLOR_DEFAULT, "%d %s from %s\n", testsLen, testInfo, mCaseName.c_str());

    if (mFailedCnt > 0)
    {
        return UT_FAIL;
    }

    return UT_OK;    
}


/***************************************************************************
 * Unit test class implement
 ***************************************************************************/

UnitTest::UnitTest()
    :mPassedCnt(0)
    ,mFailedCnt(0)
    ,mCurrentTestCase(NULL)
{
    mTestCasees.clear();
}

UnitTest::~UnitTest()
{
    size_t testsLen = mTestCasees.size();
    for (size_t i = 0; i < testsLen; i++)
    {
        TestCase* tc = mTestCasees[i];

        if (tc)
        {
            delete tc;
        }
    }

    mTestCasees.clear();
}

UnitTest* UnitTest::getInstance()
{
    static UnitTest gUTestInstance;

    return &gUTestInstance;
}

Test* UnitTest::registerTest(const char* caseName, Test* item)
{
    TestCase* tc = findTestCaseWithName(caseName);

    if (tc)
    {
        mTotalTestCnt++;
        return tc->addTest(item);
    }
    else
    {
        ColoredPrintf(COLOR_RED, "error: not fount valid test case(%s)\n", caseName);
    }

    return NULL;
}

TestCase* UnitTest::findTestCaseWithName(const char* caseName)
{
    size_t caseLen = mTestCasees.size();
    for (size_t i = 0; i < caseLen; i++)
    {
        TestCase* tc = mTestCasees[i];

        if (tc && (strcmp(tc->getCaseName(), caseName) == 0))
        {
            return tc;
        }
    }

    TestCase* caseItem = new TestCase(caseName);
    mTestCasees.push_back(caseItem);
    return caseItem;
}

const TestCase* UnitTest::getCurrentTestCase() const
{
    return mCurrentTestCase;
}

size_t UnitTest::getTotalTestCount()
{
    return mTotalTestCnt;
}


void UnitTest::outputPassedLog()
{
    ColoredPrintf(COLOR_GREEN, "[  PASSED  ]");
    ColoredPrintf(COLOR_DEFAULT, "%d %s.\n", mPassedCnt, formatTestCount(mPassedCnt));
}

void UnitTest::outputFailedLog(vector<TestCase*>& failedTestCases)
{
    size_t failedCaseCnt = failedTestCases.size();
    ColoredPrintf(COLOR_RED,"[  FAILED  ]");
    ColoredPrintf(COLOR_DEFAULT, "%d %s, listed below:\n", mFailedCnt, formatTestCount(mFailedCnt));

    for (size_t i = 0; i < failedCaseCnt; i++)
    {
        TestCase* tc = failedTestCases[i];
        if (tc)
        {
            vector<Test*> testList = tc->getTestList();
            size_t testCnt = testList.size();

            for (size_t j = 0; j < testCnt; j++)
            {
                Test* t = testList[j];
                if (t && (t->getTestResult() != UT_OK))
                {
                    ColoredPrintf(COLOR_RED,"[  FAILED  ]");
                    ColoredPrintf(COLOR_DEFAULT, "%s.%s\n",tc->getCaseName(),t->getTestName()); 
                }
            }
        }
    }  

    ColoredPrintf(COLOR_DEFAULT, "\n%d FAILED %s\n", 
        mFailedCnt, (mFailedCnt > 1)? "TESTS" : "TEST");

}

int UnitTest::runUnitTest()
{
    int caseRet = UT_OK;
    int unitTestRet = UT_OK;
    size_t testCaseLen = mTestCasees.size();
    size_t totalTestCnt = getTotalTestCount();
    vector<TestCase*> failedTestCases;
    ColoredPrintf(COLOR_GREEN, "[==========]");
    ColoredPrintf(COLOR_DEFAULT, "Running %d %s from %d %s\n",
        totalTestCnt, formatTestCount(totalTestCnt), 
        testCaseLen, formatTestCaseCount(testCaseLen));

    
    for (size_t i = 0; i < testCaseLen; i++)
    {
        mCurrentTestCase = mTestCasees[i];

        if (mCurrentTestCase)
        {
            caseRet = mCurrentTestCase->runCase();

            if (UT_OK != caseRet)
            {
                failedTestCases.push_back(mCurrentTestCase);
            }
            //todo
            mPassedCnt += mCurrentTestCase->getPassedCount();
            mFailedCnt += mCurrentTestCase->getFailedCount();
        }
    }
    
    ColoredPrintf(COLOR_GREEN, "[==========]");
    ColoredPrintf(COLOR_DEFAULT,"%d %s from %d %s ran\n",
        totalTestCnt, formatTestCount(totalTestCnt),
        testCaseLen, formatTestCaseCount(testCaseLen));

    if (mPassedCnt > 0)
    {
        outputPassedLog();
    }

    if (mFailedCnt > 0)
    {
        outputFailedLog(failedTestCases);
        unitTestRet = UT_FAIL;
    }


    return unitTestRet;
}

/***************************************************************************
*assert result class implement
***************************************************************************/
AssertionResult::AssertionResult(const AssertionResult& other)
    :mSuccess(other.mSuccess)
{
    //todo
}

/***************************************************************************
*compare function implement
***************************************************************************/
#define SUTEST_IMPL_CMP_HELPER_L(op_name, op)\
    bool CmpHelper##op_name(const char* expr1, const char* expr2, int error, \
    const char* fileName, int line, const BIGGEST_INT val1, const BIGGEST_INT val2) {\
        if (val1 op val2) {\
            return true; \
        }else {\
            ::simpleUnitTest::UnitTest::getInstance()->getCurrentTestCase()->getCurrentTest()->setTestResult(error); \
            cout << "[" << fileName << "," << line << "]" << "Error:" << endl; \
            cout << "Expected:(" << expr1 << ") " #op " (" << expr2 << ")" << endl; \
            cout << "Actual:" << val1 << " vs " << val2 << endl; \
        }\
        return false; \
    }

// Implements the helper function for {ASSERT|EXPECT}_EQ with int or
// enum arguments.
SUTEST_IMPL_CMP_HELPER_L(EQ, == )

// Implements the helper function for {ASSERT|EXPECT}_NE with int or
// enum arguments.
SUTEST_IMPL_CMP_HELPER_L(NE, != )
// Implements the helper function for {ASSERT|EXPECT}_LE with int or
// enum arguments.
SUTEST_IMPL_CMP_HELPER_L(LE, <= )
// Implements the helper function for {ASSERT|EXPECT}_LT with int or
// enum arguments.
SUTEST_IMPL_CMP_HELPER_L(LT, <)
// Implements the helper function for {ASSERT|EXPECT}_GE with int or
// enum arguments.
SUTEST_IMPL_CMP_HELPER_L(GE, >= )
// Implements the helper function for {ASSERT|EXPECT}_GT with int or
// enum arguments.
SUTEST_IMPL_CMP_HELPER_L(GT, >)
}
